/**
 * Copyright Hannes Markschlaeger, Florian Schweitzer
 * File created 13.03.2012
 * File extended by Florian Schweitzer (2012-12-20)
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version. 
 */
package org.openuat.android.service;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;

import org.openuat.android.Constants;
import org.openuat.android.OpenUATPreferencesActivity;
import org.openuat.android.OpenUAT_ID;
import org.openuat.android.authentication.AndroidAuthenticationSession;
import org.openuat.android.channel.main.bluetooth.AndroidRFCOMMChannel;
import org.openuat.android.service.connectiontype.Bluetooth;
import org.openuat.android.service.connectiontype.IConnectionType;
import org.openuat.android.service.interfaces.IConnectionCallback;
import org.openuat.android.service.interfaces.IDeviceAuthenticator;
import org.openuat.android.service.interfaces.IPreferencesCallback;

import android.app.NotificationManager;
import android.app.Service;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.StrictMode;
import android.preference.PreferenceManager;
import android.util.Log;
import android.widget.Toast;

/**
 * The OpenUATService
 * 
 * 
 * @author Hannes Markschlaeger, Florian Schweitzer
 */
public class OpenUATService extends Service {
	/**
	 * The context of the service
	 */
	public static Context context = null;
	
	/**
	 * The notification manager for informing the user
	 */
	public static NotificationManager mNotificationManager;
	
	/**
	 * The string representation of the OOB-key
	 */
	public static String oob_key = null;
	
	/**
	 * The table for preference callbacks
	 */
	public static Hashtable<String,IPreferencesCallback> prefCallbackTable;

	/**
	 * Constructor
	 */
	public OpenUATService() {
		Log.i("OpenUATService", "ctor");
	}
	
	@Override
	public void onCreate() {
		super.onCreate();
		
		StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
	    StrictMode.setThreadPolicy(policy);
		
		context = getApplicationContext();
		mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
		
		if (prefCallbackTable == null) {
			prefCallbackTable = new Hashtable<String,IPreferencesCallback>();
		}
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see android.app.Service#onBind(android.content.Intent)
	 */
	@Override
	public final IBinder onBind(final Intent arg0) {
		Log.i(this.toString(), "binded");
		
		return deviceAuthenticator;
	}
	
	/* (non-Javadoc)
	 * @see android.app.Service#onUnbind(android.content.Intent)
	 */
	public boolean onUnbind (Intent intent) {
		Log.i(this.toString(),"onUnbind is called");
		return true;
	}

	/** The device authenticator. */
	private final IDeviceAuthenticator.Stub deviceAuthenticator = new IDeviceAuthenticator.Stub() {
		@Override
		public void authenticate(final String serviceId, final String device)
				throws RemoteException {
			Log.i(this.toString(), "authenticate" + device);

			OpenUAT_ID id = OpenUAT_ID.deserialize(device);

			if (id != null) {
				final Client c = id.getApp().getClientById(id);
				
				if (c != null) {
					Thread t = new Thread(new Runnable() {

						@Override
						public void run() {
							try {
								c.establishConnection();						
							} catch (RemoteException e) {
								e.printStackTrace();
							} catch (IOException e) {
								e.printStackTrace();
							}
						}
					});
					t.start();					
				}
			}
		}

		@Override
		public String[] getAvailableClients(final String serviceId, final String connTypeString)
				throws RemoteException {
			Log.i(this.toString(), " getAvailableClients");
			
			IConnectionType.CONNECTION_TYPE connType = IConnectionType.CONNECTION_TYPE.valueOf(connTypeString);
			String[] result = null;
			
			switch (connType) {
			case WIFI:
				final List<Client> list = RegisteredAppManager.getServiceByNameAndConnType(serviceId,connType).getClients();
				
				final List<String> resultWifi = new ArrayList<String>(list.size());

				for (final Client c : list) {
					if (!c.isLocalClient()) {
						resultWifi.add(c.toString());
					}
					Log.i(this.toString(), c.toString());
				}
				result = resultWifi.toArray(new String[resultWifi.size()]);
				break;
				
			case BLUETOOTH:
				//Retrieve the bluetooth address				
				String[] temp = serviceId.split(Constants.BT_NAME_SEP + Constants.UNDERSCORE);
				
				if (temp != null && temp.length > 0) {
					String btAddress = temp[temp.length-1];
					BluetoothDevice remoteDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(btAddress);
					AndroidRFCOMMChannel channel = new AndroidRFCOMMChannel(remoteDevice);
					final List<OpenUAT_ID> openUATIdList = channel.getReceivedOpenUATIds();
					
					if (openUATIdList != null) {
						final List<String> resultBluetooth = new ArrayList<String>(openUATIdList.size());
						
						for (final OpenUAT_ID id : openUATIdList) {
							resultBluetooth.add(id.serialize());
						}		
						result = resultBluetooth.toArray(new String[resultBluetooth.size()]);
					}	
					else {
						Log.i(this.toString(),"openUATIdList is null");
					}
				}
			}
			return result;
		}
		
		@Override
		public String[] getAvailableDevices(String _connType)
				throws RemoteException {
			
			IConnectionType.CONNECTION_TYPE connType = IConnectionType.CONNECTION_TYPE.valueOf(_connType);
			
			switch (connType) {
			case BLUETOOTH:
				
				final List<BluetoothDevice> list = Bluetooth.getFoundOpenUATDevices();
				final List<String> result = new ArrayList<String>(list.size());
				
				for (final BluetoothDevice device : list) {
					result.add(Constants.BT_NAME_SEP + device.getName() + Constants.BT_NAME_SEP
							+ Constants.UNDERSCORE + device.getAddress());
				}
				return result.toArray(new String[result.size()]);
			}
			return null;
		}

		@Override
		public void register(final String serviceId,
				IConnectionCallback connectionCallback, String connTypeString, String authTypeString) throws RemoteException {

			if (serviceId == null || serviceId.length() == 0) {
				Log.e(this.toString(), "invalid serviceId");
				throw new RemoteException();
			}
			
			IConnectionType.CONNECTION_TYPE connType = IConnectionType.CONNECTION_TYPE.valueOf(connTypeString.toUpperCase());
			AndroidAuthenticationSession.AUTHENTICATION_TYPE authType = AndroidAuthenticationSession.AUTHENTICATION_TYPE.valueOf(authTypeString.toUpperCase());

			RegisteredApp app = RegisteredAppManager.getServiceByNameAndConnType(serviceId,connType);
			
			if (app == null) {
				app = new RegisteredApp(serviceId, connType, authType);
				app.setConnectionCallback(connectionCallback);
				RegisteredAppManager.registerService(app);
				
				Toast.makeText(context, "Registered App: " + serviceId + "\n" +
								"Used Connection-Type: " + connTypeString + "\n" +
								"Used Authentication-Type: " + authTypeString, Toast.LENGTH_SHORT).show();
				
				if (!prefCallbackTable.containsKey(serviceId)) {
					prefCallbackTable.remove(serviceId);				
				}
								
				AndroidAuthenticationSession.startAuthenticationProcess(app);
			}

			Log.i(this.toString(), app + " registered");
		}

		@Override
		public void registerPreferencesCallback(String serviceId, IPreferencesCallback prefCallback)
				throws RemoteException {
			if (serviceId != null && prefCallback != null) {
				
				if (!prefCallbackTable.containsKey(serviceId)) {
					prefCallbackTable.put(serviceId,prefCallback);
					Log.i(this.toString(),"registered app '" + serviceId + "' for preferences callback");
				}
				
				Intent optionsIntent = new Intent(OpenUATService.this,OpenUATPreferencesActivity.class);
				optionsIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
				startActivity(optionsIntent);				
			}
		}

		@Override
		public void disableDefaultSettings() throws RemoteException {
			SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(OpenUATService.context);		
		    SharedPreferences.Editor prefEditor = settings.edit();
		    prefEditor.putBoolean("defaultSettingsEnable", false);
		    prefEditor.commit();
		}

		@Override
		public String[] getAvailableConnectionTypes() throws RemoteException {
			IConnectionType.CONNECTION_TYPE[] connTypes = IConnectionType.CONNECTION_TYPE.values();
			String[] connTypeNames = new String[connTypes.length];
			
			for (int i = 0; i < connTypeNames.length ; i++) {
				connTypeNames[i] = connTypes[i].name().toString();
			}
			
			return connTypeNames;
		}

		@Override
		public String[] getAvailableAuthenticationTypes()
				throws RemoteException {
			AndroidAuthenticationSession.AUTHENTICATION_TYPE[] authTypes = AndroidAuthenticationSession.AUTHENTICATION_TYPE.values();
			String[] authTypeNames = new String[authTypes.length];
			
			for (int i = 0; i < authTypeNames.length ; i++) {
				authTypeNames[i] = authTypes[i].name().toString();
			}
			
			return authTypeNames;
		}

		@Override
		public void unregisterPreferencesCallback(String serviceId)
				throws RemoteException {
			if (prefCallbackTable.contains(serviceId)) {
				prefCallbackTable.remove(serviceId);
			}
		}
	};

	@Override
	public void onDestroy() {
		super.onDestroy();
		prefCallbackTable.clear();
		Log.i(this.toString(),"onDestroy is called");
		AndroidAuthenticationSession.stopAuthenticationProcess();
	}
}